
#ifndef A8_LUAENGINE_H
#define A8_LUAENGINE_H

#include <a8/compat.hpp>
#include <a8/xvalue.hpp>
#include <a8/delegate.hpp>
extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>  
};

namespace a8
{
    class LuaEngine
    {
    public:
        a8::Delegate<void (int errorID, std::string& errorMsg)> onLuaError;
        
        LuaEngine()
        {
            mLuaState = luaL_newstate();
            assert(mLuaState);
            luaL_openlibs(mLuaState);
        }
        
        ~LuaEngine()
        {
            if (mLuaState){
                lua_close(mLuaState);
                mLuaState = NULL;
            }
        }
        
        lua_State* LuaState()
        {	
            return mLuaState;
        }
        
        int LoadFromBuffer(const char* szBuffer)
        {
            int iError = luaL_loadbuffer(mLuaState, szBuffer, strlen(szBuffer), "line") 
                || lua_pcall(mLuaState, 0, 0, 0);
            if (iError != 0){
				std::string errMsg(lua_tostring(mLuaState, -1));
				onLuaError(0, errMsg);
                lua_pop(mLuaState, 1);
            }
            return iError;
        }
        
        bool LoadFromFile(const char* szFile)
        {
            int iError = luaL_loadfile(mLuaState, szFile)
                || lua_pcall(mLuaState, 0, 0, 0);
            if (iError != 0){
				std::string errMsg(lua_tostring(mLuaState, -1));
				onLuaError(0, errMsg);
                lua_pop(mLuaState, 1);
            }
            return iError == 0;
        }
        
        a8::XValue CallFunc(const char* funcName)
        {
            a8::XValue result;
            CallLuaFunc(funcName, NULL, 0, result);
            return result;
        }
        
        template<typename _T0_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0)};
            CallLuaFunc(funcName, args, 1, result);
            return result;
        }
        
        template<typename _T0_,
                 typename _T1_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0,
                            const _T1_& t1)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1)};
            CallLuaFunc(funcName, args, 2, result);
            return result;
        }
        
        template<typename _T0_,
                 typename _T1_,
                 typename _T2_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0,
                            const _T1_& t1,
                            const _T2_& t2)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2)};
            CallLuaFunc(funcName, args, 3, result);
            return result;
        }


        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0,
                            const _T1_& t1,
                            const _T2_& t2,
                            const _T3_& t3)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3)};
            CallLuaFunc(funcName, args, 4, result);
            return result;
        }

        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0,
                            const _T1_& t1,     
                            const _T2_& t2,
                            const _T3_& t3,
                            const _T4_& t4)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4)};
            CallLuaFunc(funcName, args, 5, result);
            return result;
        }

        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_,
                 typename _T5_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0,
                            const _T1_& t1,
                            const _T2_& t2,
                            const _T3_& t3,
                            const _T4_& t4,
                            const _T5_& t5)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4),
                                 a8::XValue(t5)};
            CallLuaFunc(funcName, args, 6, result);
            return result;
        }

        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_,
                 typename _T5_,
                 typename _T6_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0,
                            const _T1_& t1,
                            const _T2_& t2,
                            const _T3_& t3,
                            const _T4_& t4,
                            const _T5_& t5,
                            const _T6_& t6)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4),
                                 a8::XValue(t5), a8::XValue(t6)};
            CallLuaFunc(funcName, args, 7, result);
            return result;
        }

        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_,
                 typename _T5_,
                 typename _T6_,
                 typename _T7_>
        a8::XValue CallFunc(const char* funcName, const _T0_& t0,
                            const _T1_& t1,
                            const _T2_& t2,
                            const _T3_& t3,
                            const _T4_& t4,
                            const _T5_& t5,
                            const _T6_& t6,
                            const _T7_& t7)
        {
            a8::XValue result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4),
                                 a8::XValue(t5), a8::XValue(t6), a8::XValue(t7)};
            CallLuaFunc(funcName, args, 8, result);
            return result;
        }

    private:

        void CallLuaFunc(const char* funcName, a8::XValue* args, int argNum, a8::XValue& result)
        {
            char buf[100];
            lua_getglobal(mLuaState, funcName);
            for (int i = 0; i < argNum; i++){
                switch(args[i].type()){
                case XVT_INT: 
                case XVT_UINT:
                    lua_pushnumber(mLuaState, args[i].GetInt());
                    break;
                case XVT_INT64:	
                    XItoa(args[i].GetInt64(), buf, 100, 10);
                    lua_pushstring(mLuaState, buf);
                    break;
                case XVT_UINT64:
                    XItoa(args[i].GetUInt64(), buf, 100, 10);
                    lua_pushstring(mLuaState, buf);
                    break;
                case XVT_STRING:
                    lua_pushstring(mLuaState, args[i].GetString().c_str());
                    break;
                }
            }
            if (lua_pcall(mLuaState, argNum, 1, NULL) != 0){
				std::string errMsg(lua_tostring(mLuaState, -1));
				onLuaError(0, errMsg);
                lua_pop(mLuaState, 1);
                return;
            }	
            switch(lua_type(mLuaState, 1)){
            case LUA_TBOOLEAN:
                result.Set(lua_toboolean(mLuaState, 1));
                break;
            case LUA_TNUMBER:
                result.Set((long long)lua_tonumber(mLuaState, 1));
                break;
            case LUA_TSTRING:
                result.Set((long long)lua_tostring(mLuaState, 1));
                break;
            }
        }
        
    private:
        lua_State* mLuaState;
    };
}

#endif
